// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include <pybind11/pybind11.h>

#include "appcoin2/support/py_module/driver/py_feed_update.h"
#include "appcoin2/support/py_module/driver/py_order_update.h"
#include "coin2/exchange/base/log/strategy_logger.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/strategy/strategy.h"
#include "coin2/strategy/util/latency_util.h"

namespace coin2::exchange::driver {

namespace impl {

namespace py = pybind11;
using base::feed::FeedUpdate;
using coin2::exchange::base::order::OrderUpdate;
using coin2::exchange::base::strategy_util::StrategyLogger;
using coin2::exchange::base::strategy_util::TelemetryLogger;

class DirectForwarder : public ::coin2::strategy::IStrategy {
 public:
  explicit DirectForwarder(py::object strat, bool enable_telemetry)
      : strat_(strat), enable_telemetry_{enable_telemetry} {}

  // feed
  void onBookFeed(const FeedUpdate& upd) override {
    if (!py::hasattr(strat_, "on_book_feed")) {
      return;
    }
    PyBookView book(&upd);
    strat_.attr("on_book_feed")(book);

    if (enable_telemetry_) {
      auto acc_request = GetAccRequest(upd.mea());
      auto feed_latency_tag = GetFeedLatencyTag(upd);
      coin2::strategy::util::SetFeedTimeToLatency(
          strat_logger_.get(),
          feed_mgr_,
          upd,
          feed_latency_tag,
          acc_request,
          true);
      coin2::strategy::util::SetFeedTimeToLatency(
          strat_logger_.get(),
          feed_mgr_,
          upd,
          coin::proto::LatencyProto::ORDER_LATENCY,
          acc_request,
          false);
    }
  }

  void onTradeFeed(const FeedUpdate& upd) override {
    if (!py::hasattr(strat_, "on_trade_feed")) {
      return;
    }
    PyTradeView trade(&upd);
    strat_.attr("on_trade_feed")(trade);
  }

  void onLiquidationFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_liquidation_feed")) {
      return;
    }
    // TODO:
  }

  void onOpenInterestFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_open_interest_feed")) {
      return;
    }
    // TODO:
  }

  void onMarkPriceFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_mark_price_feed")) {
      return;
    }
    // TODO:
  }

  void onIndexFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_index_feed")) {
      return;
    }
    // TODO:
  }

  void onFundingRateFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_funding_rate_feed")) {
      return;
    }
    // TODO:
  }

  void onIvGreeksFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_iv_greeks_feed")) {
      return;
    }
    // TODO:
  }

  void onNavFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_nav_feed")) {
      return;
    }
    // TODO:
  }

  void onKlineFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_kline_feed")) {
      return;
    }
    // TODO:
  }

  void onWalletFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_wallet_feed")) {
      return;
    }
    // TODO:
  }

  void onMarketAnalysisFeed(const exchange::base::feed::FeedUpdate&) override {
    if (!py::hasattr(strat_, "on_market_analysis_feed")) {
      return;
    }
    // TODO:
  }

  // void onHeartbeatFeed(const FeedTimer& timer) override {
  //   if (!py::hasattr(strat_, "on_heartbeat_feed")) {
  //     return;
  //   }
  //   // TODO:
  // }

  // order
  void onAccountOrder(const OrderUpdate& upd) override {
    if (!py::hasattr(strat_, "on_account_order")) {
      return;
    }
    PyOrderUpdate order(&upd);
    strat_.attr("on_account_order")(order);
  }

  void onAccountInfo(const OrderUpdate& upd) override {
    if (!py::hasattr(strat_, "on_account_info")) {
      return;
    }
    PyAccountInfo info(&upd);
    strat_.attr("on_account_info")(info);
  }

  void onOrderSubsystemError(const exchange::base::order::OrderUpdate&) override {
    if (!py::hasattr(strat_, "on_order_subsystem_error")) {
      return;
    }
    // TODO:
  }

  void onOrderLog(const coin::proto::OrderGatewayLog&) override {
    if (!py::hasattr(strat_, "on_order_log")) {
      return;
    }
    // TODO:
  }

  void Init(::coin2::app::Driver* driver) override {
    if (enable_telemetry_) {
      const auto& strategy_config = driver->strategy()->config();
      coin::proto::StrategyRequestProto strat_request;
      strat_request.set_strategy_group(strategy_config.strategy_group());
      strat_request.set_strategy_name(strategy_config.strategy_name());

      const auto& strat_logger_config = strategy_config.strategy_logger_config();
      const auto& telemetry_logger_config = strategy_config.telemetry_logger_config();
      strat_logger_ = std::make_unique<StrategyLogger>(
          strat_request,
          strat_logger_config.log_root(),
          strat_logger_config.machine());
      telemetry_logger_ = std::make_unique<TelemetryLogger>(
          strat_request,
          telemetry_logger_config.log_root(),
          telemetry_logger_config.machine());
      strat_logger_->AddCallback(std::bind(
          &TelemetryLogger::CondWrite,
          telemetry_logger_.get(),
          std::placeholders::_1,
          std::placeholders::_2,
          std::placeholders::_3));

      const auto& systems = driver->order()->GetAllSystems();
      for (const auto& os : systems) {
        os->gateway()->set_latency_recorder(strat_logger_->mutable_latency_recorder());
      }
    }
  }

  void onCleanup(::coin2::app::Driver* driver) override {
    // TODO:
  }

  void InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) {
    feed_mgr_ = feed_mgr;
  }

 private:
  auto GetFeedLatencyTag(const FeedUpdate& upd) -> coin::proto::LatencyProto::LatencyTag {
    if (upd.is_book())
      return coin::proto::LatencyProto::BOOK_FEED_LATENCY;
    else if (upd.is_trade())
      return coin::proto::LatencyProto::TRADE_FEED_LATENCY;
    else
      return coin::proto::LatencyProto::FEED_LATENCY;
  }

  auto GetAccRequest(const MarketExchangeApi& mea) -> coin::proto::AccountRequestProto {
    coin::proto::AccountRequestProto acc_request;
    acc_request.set_market_type(MarketType_Name(mea.market));
    acc_request.set_exchange(ExchangeType_Name(mea.exchange));
    acc_request.set_api_version(mea.api);
    return acc_request;
  }

 private:
  py::object strat_;

  // telemetry
  std::unique_ptr<StrategyLogger> strat_logger_ = {};
  std::unique_ptr<TelemetryLogger> telemetry_logger_ = {};
  const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr_ = nullptr;
  bool enable_telemetry_ = false;
};

}  // namespace impl

using impl::DirectForwarder;
}  // namespace coin2::exchange::driver
